/*
 * Copyright (c) 2009, Michael van der Westhuizen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#ifndef MQCONNECTION_MCVDW_20090517_C
#define MQCONNECTION_MCVDW_20090517_C

#include "imq.h"

#include "mqstatus.c"
#include "util.c"
#include "mqdestination.c"
#include "mqmessage.c"
#include "mqproducer.c"
#include "mqconsumer-callbacks.c"
#include "mqconsumer.c"
#include "mqsession.c"
#include "mqconnection-callbacks.c"



static int mqcrt_MQConnection_stop_and_close(mqcrt_MQConnection *self, int raise_errors)
{
    MQStatus status;

    if (!HANDLE_IS_VALID(self->connection)) {
        return 1;
    }

    if (!self->stopped) {
        status = MQStopConnection(self->connection);

        if (raise_errors) {
            if (setExceptionFromStatus(status)) {
                return 0;
            }
        }

        self->stopped = 1;
    }

    if (!self->closed) {
        status = MQCloseConnection(self->connection);

        if (raise_errors) {
            if (setExceptionFromStatus(status)) {
                return 0;
            }
        }

        self->closed = 0;
    }

    return 1;
}


static int mqcrt_MQConnection_traverse(mqcrt_MQConnection *self, visitproc visit, void *arg)
{
    DPRINTF(("INFO: mqcrt_MQConnection_traverse: %s\n", "entry"));

    if (self->cb_data) {
        Py_VISIT(self->cb_data->callable);
        Py_VISIT(self->cb_data->args);
    }

    DPRINTF(("INFO: mqcrt_MQConnection_traverse: %s\n", "exit"));
    return 0;
}


static int mqcrt_MQConnection_clear(mqcrt_MQConnection *self)
{
    DPRINTF(("INFO: mqcrt_MQConnection_clear: %s\n", "entry"));

    if (self->cb_data) {
        Py_CLEAR(self->cb_data->callable);
        Py_CLEAR(self->cb_data->args);
    }

    DPRINTF(("INFO: mqcrt_MQConnection_clear: %s\n", "exit"));
    return 0;
}


static void mqcrt_MQConnection_dealloc(mqcrt_MQConnection *self)
{
    DPRINTF(("%s\n", "INFO: mqcrt_MQConnection_dealloc: entry"));

    mqcrt_MQConnection_stop_and_close(self, 0);

    if (HANDLE_IS_VALID(self->connection)) {
        MQFreeConnection(self->connection);
        INIT_HANDLE(self->connection);
    }

    if (self->cb_data) {
        self->cb_data->connection = NULL;
        Py_CLEAR(self->cb_data->callable);
        Py_CLEAR(self->cb_data->args);
        PyMem_Free(self->cb_data);
        self->cb_data = NULL;
    }

    self->ob_type->tp_free((PyObject*)self);
    DPRINTF(("%s\n", "INFO: mqcrt_MQConnection_dealloc: exit"));
}


static PyObject * mqcrt_MQConnection_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    mqcrt_MQConnection * self;
    connectionExceptionListenerData * data;

    DPRINTF(("INFO: mqcrt_MQConnection_new: %s\n", "entry"));

    if (NULL == (data = (connectionExceptionListenerData *)PyMem_Malloc(sizeof(connectionExceptionListenerData)))) {
        PyErr_SetFromErrno(PyExc_MemoryError);
        return NULL;
    }

    data->callable = NULL;
    data->args = NULL;

    if (NULL == (self = (mqcrt_MQConnection *)type->tp_alloc(type, 0))) {
        PyMem_Free(data);
        return NULL;
    }

    data->connection = (PyObject *)self;
    self->cb_data = data;
    self->closed = 1;
    self->stopped = 1;
    self->xa = 0;
    INIT_HANDLE(self->connection);

    DPRINTF(("INFO: mqcrt_MQConnection_new: %s\n", "exit"));
    return (PyObject *)self;
}


#ifdef XA_DEVELOPMENT
static int mqcrt_MQXAConnection_init(mqcrt_MQConnection *self, PyObject *args, PyObject *kwds)
{
    DPRINTF(("INFO: mqcrt_MQXAConnection_init: %s\n", "entry"));

    if (!mqcrt_MQConnection_stop_and_close(self, 1)) {
        return -1;
    }

    if (HANDLE_IS_VALID(self->connection)) {
        if (setExceptionFromStatus(MQFreeConnection(self->connection))) {
            return -1;
        }

        INIT_HANDLE(self->connection);
    }

    Py_CLEAR(self->cb_data->callable);
    Py_CLEAR(self->cb_data->args);

    self->stopped = 1;
    self->closed = 1;
    self->xa = 1;

    if (setExceptionFromStatus(MQGetXAConnection(&self->connection))) {
        return -1;
    }

    self->closed = 0;
    DPRINTF(("INFO: mqcrt_MQXAConnection_init: %s\n", "exit"));
    return 0;
}
#endif


static int mqcrt_MQConnection_init(mqcrt_MQConnection *self, PyObject *args, PyObject *kwds)
{
    PyObject * props_dict;
    MQPropertiesHandle properties;
    PyObject * username;
    PyObject * password;
    PyObject * clientID;
    ConstMQString clientID_;
    PyObject * exceptionListener;
    MQConnectionExceptionListenerFunc listener;
    PyObject * listenerCallBackData;
    void * data;
    MQStatus status;

    static char *kwlist[] = {"properties", "username", "password", "clientID", "exceptionListener", "listenerCallBackData", NULL};

    DPRINTF(("INFO: mqcrt_MQConnection_init: %s\n", "entry"));

    if (!mqcrt_MQConnection_stop_and_close(self, 1)) {
        return -1;
    }

    if (HANDLE_IS_VALID(self->connection)) {
        if (setExceptionFromStatus(MQFreeConnection(self->connection))) {
            return -1;
        }

        INIT_HANDLE(self->connection);
    }

    Py_CLEAR(self->cb_data->callable);
    Py_CLEAR(self->cb_data->args);

    self->closed = 1;
    self->stopped = 1;
    self->xa = 0;
    props_dict = username = password = clientID = exceptionListener = listenerCallBackData = NULL;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOO|OOO", kwlist, &props_dict, &username, &password, &clientID, &exceptionListener, &listenerCallBackData)) {
        return -1;
    }

    INIT_HANDLE(properties);

    if (0 != dictToProperties(&properties, props_dict)) {
        return -1;
    }

    if (NULL != exceptionListener && Py_None != exceptionListener) {
        if (!PyCallable_Check(exceptionListener)) {
            PyErr_SetString(PyExc_TypeError, "exceptionListener must be callable");
            MQFreeProperties(properties);
            return -1;
        }
    }

    if (NULL == (username = coerceObjectToUtf8String(username))) {
        MQFreeProperties(properties);
        return -1;
    }

    if (NULL == (password = coerceObjectToUtf8String(password))) {
        Py_CLEAR(username);
        MQFreeProperties(properties);
        return -1;
    }

    clientID_ = NULL;

    if (NULL != clientID && Py_None != clientID) {
        if (NULL == (clientID = coerceObjectToUtf8String(clientID))) {
            Py_CLEAR(username);
            Py_CLEAR(password);
            MQFreeProperties(properties);
            return -1;
        }

        if (NULL == (clientID_ = PyString_AsString(clientID))) {
            Py_CLEAR(username);
            Py_CLEAR(password);
            MQFreeProperties(properties);
            return -1;
        }
    }

    listener = NULL;
    data = NULL;

    if (NULL != exceptionListener && Py_None != exceptionListener) {
        listener = (MQConnectionExceptionListenerFunc)connectionExceptionListenerFunc;
        self->cb_data->callable = exceptionListener;
        Py_INCREF(self->cb_data->callable);
        self->cb_data->args = listenerCallBackData;
        Py_XINCREF(self->cb_data->args);
        data = (void *)self->cb_data;
    }

    status = MQCreateConnection(properties, PyString_AsString(username), PyString_AsString(password),
        clientID_, listener, data, &self->connection);

    Py_CLEAR(username);
    Py_CLEAR(password);
    Py_CLEAR(clientID);

    if (setExceptionFromStatus(status)) {
        Py_CLEAR(self->cb_data->callable);
        Py_CLEAR(self->cb_data->args);
        MQFreeProperties(properties);
        return -1;
    }

    INIT_HANDLE(properties);
    self->closed = 0;
    DPRINTF(("INFO: mqcrt_MQConnection_init: %s\n", "exit"));
    return 0;
}


static PyObject * mqcrt_MQConnection_stop(mqcrt_MQConnection *self, PyObject *args, PyObject *kwds)
{
    if (!self->stopped) {
        if (setExceptionFromStatus(MQStopConnection(self->connection))) {
            return NULL;
        }

        self->stopped = 1;
    }

    Py_RETURN_NONE;
}


static PyObject * mqcrt_MQConnection_close(mqcrt_MQConnection *self, PyObject *args, PyObject *kwds)
{
    if (self->closed) {
        DPRINTF(("%s\n", "WARN: mqcrt_MQConnection_close: already closed"));
        Py_RETURN_NONE;
    }

    DPRINTF(("%s\n", "INFO: mqcrt_MQConnection_close: entry"));

    if (setExceptionFromStatus(MQCloseConnection(self->connection))) {
        DPRINTF(("%s\n", "CRIT: mqcrt_MQConnection_close: exit [error]"));
        return NULL;
    }

    self->closed = 1;
    DPRINTF(("%s\n", "INFO: mqcrt_MQConnection_close: exit"));
    Py_RETURN_NONE;
}


static PyObject * mqcrt_MQConnection_start(mqcrt_MQConnection *self, PyObject *args, PyObject *kwds)
{
    if (self->stopped) {
        if (setExceptionFromStatus(MQStartConnection(self->connection))) {
            return NULL;
        }

        self->stopped = 0;
    }

    Py_RETURN_NONE;
}


static PyObject * mqcrt_MQConnection_create_session(mqcrt_MQConnection *self, PyObject *args, PyObject *kwds)
{
    MQSessionHandle handle;
    PyObject * session;
    PyObject * isTransacted;
    MQAckMode acknowledgeMode;
    MQReceiveMode receiveMode;

    static char *kwlist[] = {"is_transacted", "acknowledge_mode", "receive_mode", NULL};

    session = NULL;
    INIT_HANDLE(handle);

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oii", kwlist, &isTransacted, &acknowledgeMode, &receiveMode)) {
        return NULL;
    }

    if (setExceptionFromStatus(MQCreateSession(self->connection, PyObject_IsTrue(isTransacted), acknowledgeMode, receiveMode, &handle))) {
        return NULL;
    }

    if (NULL == (session = mqcrt_MQSession_Factory(handle, self))) {
        MQCloseSession(handle);
        return NULL;
    }

    return session;
}


#ifdef XA_DEVELOPMENT
/* XXX: create XA session */
#endif

static PyObject * mqcrt_MQConnection_properties(mqcrt_MQConnection *self, PyObject *args, PyObject *kwds)
{
    MQPropertiesHandle properties;
    PyObject * dict;

    INIT_HANDLE(properties);

    if (setExceptionFromStatus(MQGetConnectionProperties(self->connection, &properties))) {
        return NULL;
    }

    if (NULL == (dict = PyDict_New())) {
        MQFreeProperties(properties);
        return NULL;
    }

    if (0 != propertiesToDict(dict, properties)) {
        Py_CLEAR(dict);
        MQFreeProperties(properties);
        return NULL;
    }

    MQFreeProperties(properties);
    return dict;
}


static PyObject * mqcrt_MQConnection_metadata(mqcrt_MQConnection *self, PyObject *args, PyObject *kwds)
{
    MQPropertiesHandle metadata;
    PyObject * dict;

    INIT_HANDLE(metadata);

    if (setExceptionFromStatus(MQGetMetaData(self->connection, &metadata))) {
        return NULL;
    }

    if (NULL == (dict = PyDict_New())) {
        MQFreeProperties(metadata);
        return NULL;
    }

    if (0 != propertiesToDict(dict, metadata)) {
        Py_CLEAR(dict);
        MQFreeProperties(metadata);
        return NULL;
    }

    MQFreeProperties(metadata);
    return dict;
}


static PyMethodDef mqcrt_MQConnection_methods[] = {
    {"start", (PyCFunction)mqcrt_MQConnection_start, METH_NOARGS, "Starts the connection to the broker. This starts (or resumes) message delivery."},
    {"stop", (PyCFunction)mqcrt_MQConnection_stop, METH_NOARGS,"Stops the connection to the broker. This stops the broker from delivering messages until MQStartConnection is called."},
    {"close", (PyCFunction)mqcrt_MQConnection_close, METH_NOARGS, "Closes the connection to the broker."},
    {"create_session", (PyCFunction)mqcrt_MQConnection_create_session, METH_VARARGS | METH_KEYWORDS, "Creates a new session on this connection with the properties specified by the supplied parameters."},
    {"metadata", (PyCFunction)mqcrt_MQConnection_metadata, METH_NOARGS, "Gets the metadata for this connection."},
    {"properties", (PyCFunction)mqcrt_MQConnection_properties, METH_NOARGS, "Get the properties of a connection."},
    {NULL}
};


static PyTypeObject mqcrt_MQConnectionType = {
    PyObject_HEAD_INIT(NULL)
    0,                                      /* ob_size */
    "imq.MQConnection",                     /* tp_name */
    sizeof(mqcrt_MQConnection),             /* tp_basicsize */
    0,                                      /* tp_itemsize */
    (destructor)mqcrt_MQConnection_dealloc, /* tp_dealloc */
    0,                                      /* tp_print */
    0,                                      /* tp_getattr */
    0,                                      /* tp_setattr */
    0,                                      /* tp_compare */
    0,                                      /* tp_repr */
    0,                                      /* tp_as_number */
    0,                                      /* tp_as_sequence */
    0,                                      /* tp_as_mapping */
    0,                                      /* tp_hash */
    0,                                      /* tp_call */
    0,                                      /* tp_str */
    0,                                      /* tp_getattro */
    0,                                      /* tp_setattro */
    0,                                      /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */
    "MQConnection objects",                 /* tp_doc */
    (traverseproc)mqcrt_MQConnection_traverse, /* tp_traverse */
    (inquiry)mqcrt_MQConnection_clear,      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    mqcrt_MQConnection_methods,             /* tp_methods */
    0,                                      /* tp_members */
    0,                                      /* tp_getset */
    0,                                      /* tp_base */
    0,                                      /* tp_dict */
    0,                                      /* tp_descr_get */
    0,                                      /* tp_descr_set */
    0,                                      /* tp_dictoffset */
    (initproc)mqcrt_MQConnection_init,      /* tp_init */
    0,                                      /* tp_alloc */
    mqcrt_MQConnection_new,                 /* tp_new */
    0,
};


#ifdef XA_DEVELOPMENT
static PyTypeObject mqcrt_MQXAConnectionType = {
    PyObject_HEAD_INIT(NULL)
    0,                                      /* ob_size */
    "imq.MQXAConnection",                   /* tp_name */
    sizeof(mqcrt_MQConnection),             /* tp_basicsize */
    0,                                      /* tp_itemsize */
    (destructor)mqcrt_MQConnection_dealloc, /* tp_dealloc */
    0,                                      /* tp_print */
    0,                                      /* tp_getattr */
    0,                                      /* tp_setattr */
    0,                                      /* tp_compare */
    0,                                      /* tp_repr */
    0,                                      /* tp_as_number */
    0,                                      /* tp_as_sequence */
    0,                                      /* tp_as_mapping */
    0,                                      /* tp_hash */
    0,                                      /* tp_call */
    0,                                      /* tp_str */
    0,                                      /* tp_getattro */
    0,                                      /* tp_setattro */
    0,                                      /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */
    "MQXAConnection objects",               /* tp_doc */
    (traverseproc)mqcrt_MQConnection_traverse, /* tp_traverse */
    (inquiry)mqcrt_MQConnection_clear,      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    mqcrt_MQConnection_methods,             /* tp_methods */
    0,                                      /* tp_members */
    0,                                      /* tp_getset */
    0,                                      /* tp_base */
    0,                                      /* tp_dict */
    0,                                      /* tp_descr_get */
    0,                                      /* tp_descr_set */
    0,                                      /* tp_dictoffset */
    (initproc)mqcrt_MQXAConnection_init,    /* tp_init */
    0,                                      /* tp_alloc */
    mqcrt_MQConnection_new,                 /* tp_new */
    0,
};
#endif

#endif
